package com.pig.openg1.render;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.os.SystemClock;
import android.util.Log;

import com.pig.openg1.R;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class GraphFourRender implements GLSurfaceView.Renderer {

    private final String TAG = GraphFourRender.class.getSimpleName();

    private Context mContext;

    private float[] mModelMatrix = new float[16];
    private float[] mViewMatrix = new float[16];
    private float[] mProjectionMatrix = new float[16];
    private float[] mMVPMatrix = new float[16];
    private float[] mLightModelMatrix = new float[16];

    private FloatBuffer mCubePosition;
    private FloatBuffer mCubeColor;
    private FloatBuffer mCubeNormal;
    private FloatBuffer mCubeTexture;

    private int mMVPMatrixHandle;
    private int mViewMatrixHandle;
    private int mLightPositionHandle;
    private int mTextureHandle;
    private int mPositionHandle;
    private int mColorHandle;
    private int mNormalHandle;
    private int mTextuereCoordinateHandle;

    private int mBytePreFloat = 4;
    private int mPositionDataSize = 3;
    private int mColorDataSize = 4;
    private int mNormalDataSize = 3;
    private int mTextuewCoordinateDataSize = 2;

    private float[] mLightPositionModel = new float[]{0.0f,0.0f,0.0f,1.0f};
    private float[] mLightPositionWorld = new float[4];
    private float[] mLightPositionEys = new float[4];

    private int mProgramHandle;
    private int mPointProgramHandle;
    private int mTextureDataHandle;

    public GraphFourRender(Context mContext)
    {
        this.mContext = mContext;

        float[] mCubePositionArray = {
                -1.0f, 1.0f, 1.0f,
                -1.0f, -1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,
                -1.0f, -1.0f, 1.0f,
                1.0f, -1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,

                // Right face
                1.0f, 1.0f, 1.0f,
                1.0f, -1.0f, 1.0f,
                1.0f, 1.0f, -1.0f,
                1.0f, -1.0f, 1.0f,
                1.0f, -1.0f, -1.0f,
                1.0f, 1.0f, -1.0f,

                // Back face
                1.0f, 1.0f, -1.0f,
                1.0f, -1.0f, -1.0f,
                -1.0f, 1.0f, -1.0f,
                1.0f, -1.0f, -1.0f,
                -1.0f, -1.0f, -1.0f,
                -1.0f, 1.0f, -1.0f,

                // Left face
                -1.0f, 1.0f, -1.0f,
                -1.0f, -1.0f, -1.0f,
                -1.0f, 1.0f, 1.0f,
                -1.0f, -1.0f, -1.0f,
                -1.0f, -1.0f, 1.0f,
                -1.0f, 1.0f, 1.0f,

                // Top face
                -1.0f, 1.0f, -1.0f,
                -1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, -1.0f,
                -1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, -1.0f,

                // Bottom face
                1.0f, -1.0f, -1.0f,
                1.0f, -1.0f, 1.0f,
                -1.0f, -1.0f, -1.0f,
                1.0f, -1.0f, 1.0f,
                -1.0f, -1.0f, 1.0f,
                -1.0f, -1.0f, -1.0f
        };

        float[] mCubeColorArray = {
                // Front face (red)
                1.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 0.0f, 1.0f,

                // Right face (green)
                0.0f, 1.0f, 0.0f, 1.0f,
                0.0f, 1.0f, 0.0f, 1.0f,
                0.0f, 1.0f, 0.0f, 1.0f,
                0.0f, 1.0f, 0.0f, 1.0f,
                0.0f, 1.0f, 0.0f, 1.0f,
                0.0f, 1.0f, 0.0f, 1.0f,

                // Back face (blue)
                0.0f, 0.0f, 1.0f, 1.0f,
                0.0f, 0.0f, 1.0f, 1.0f,
                0.0f, 0.0f, 1.0f, 1.0f,
                0.0f, 0.0f, 1.0f, 1.0f,
                0.0f, 0.0f, 1.0f, 1.0f,
                0.0f, 0.0f, 1.0f, 1.0f,

                // Left face (yellow)
                1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 0.0f, 1.0f,

                // Top face (cyan)
                0.0f, 1.0f, 1.0f, 1.0f,
                0.0f, 1.0f, 1.0f, 1.0f,
                0.0f, 1.0f, 1.0f, 1.0f,
                0.0f, 1.0f, 1.0f, 1.0f,
                0.0f, 1.0f, 1.0f, 1.0f,
                0.0f, 1.0f, 1.0f, 1.0f,

                // Bottom face (magenta)
                1.0f, 0.0f, 1.0f, 1.0f,
                1.0f, 0.0f, 1.0f, 1.0f,
                1.0f, 0.0f, 1.0f, 1.0f,
                1.0f, 0.0f, 1.0f, 1.0f,
                1.0f, 0.0f, 1.0f, 1.0f,
                1.0f, 0.0f, 1.0f, 1.0f
        };

        float[] mCubeNormalArray = {
                0.0f, 0.0f, 1.0f,
                0.0f, 0.0f, 1.0f,
                0.0f, 0.0f, 1.0f,
                0.0f, 0.0f, 1.0f,
                0.0f, 0.0f, 1.0f,
                0.0f, 0.0f, 1.0f,

                // Right face
                1.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 0.0f,

                // Back face
                0.0f, 0.0f, -1.0f,
                0.0f, 0.0f, -1.0f,
                0.0f, 0.0f, -1.0f,
                0.0f, 0.0f, -1.0f,
                0.0f, 0.0f, -1.0f,
                0.0f, 0.0f, -1.0f,

                // Left face
                -1.0f, 0.0f, 0.0f,
                -1.0f, 0.0f, 0.0f,
                -1.0f, 0.0f, 0.0f,
                -1.0f, 0.0f, 0.0f,
                -1.0f, 0.0f, 0.0f,
                -1.0f, 0.0f, 0.0f,

                // Top face
                0.0f, 1.0f, 0.0f,
                0.0f, 1.0f, 0.0f,
                0.0f, 1.0f, 0.0f,
                0.0f, 1.0f, 0.0f,
                0.0f, 1.0f, 0.0f,
                0.0f, 1.0f, 0.0f,

                // Bottom face
                0.0f, -1.0f, 0.0f,
                0.0f, -1.0f, 0.0f,
                0.0f, -1.0f, 0.0f,
                0.0f, -1.0f, 0.0f,
                0.0f, -1.0f, 0.0f,
                0.0f, -1.0f, 0.0f
        };

        float[] mCubeTextureArray = {
                // Front face
                0.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 1.0f,
                1.0f, 0.0f,

                // Right face
                0.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 1.0f,
                1.0f, 0.0f,

                // Back face
                0.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 1.0f,
                1.0f, 0.0f,

                // Left face
                0.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 1.0f,
                1.0f, 0.0f,

                // Top face
                0.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 1.0f,
                1.0f, 0.0f,

                // Bottom face
                0.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 1.0f,
                1.0f, 0.0f
        };

        mCubePosition = ByteBuffer.allocateDirect(mCubePositionArray.length * mBytePreFloat)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubePosition.put(mCubePositionArray).position(0);

        mCubeColor = ByteBuffer.allocateDirect(mCubeColorArray.length * mBytePreFloat)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubeColor.put(mCubeColorArray).position(0);

        mCubeNormal = ByteBuffer.allocateDirect(mCubeNormalArray.length * mBytePreFloat)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubeNormal.put(mCubeNormalArray).position(0);

        mCubeTexture = ByteBuffer.allocateDirect(mCubeTextureArray.length * mBytePreFloat)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubeTexture.put(mCubeTextureArray).position(0);
    }


    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);
        GLES20.glEnable(GLES20.GL_CULL_FACE);
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);

        //设置摄像机位置
        Matrix.setLookAtM(mViewMatrix,0,0.0f,0.0f,-0.5f,
                0.0f,0.0f,-5.0f,0.0f,1.0f,0.0f);

        String mVertexSource = loadAssertFile("per_pixel_vertex_shader.glsl");
        String mFragmentSource = loadAssertFile("per_pixel_fragment_shader.glsl");
        int mVertexShader = loadShader(GLES20.GL_VERTEX_SHADER,mVertexSource);
        int mFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,mFragmentSource);
        if(mVertexShader == 0 )
            throw new RuntimeException("Error create vertex shader !");
        if(mFragmentShader == 0 )
            throw new RuntimeException("Error create fragment shader ! ");
        mProgramHandle = GLES20.glCreateProgram();
        if(mProgramHandle == 0 )
            throw new RuntimeException("Error create program ！ ");
        GLES20.glAttachShader(mProgramHandle,mVertexShader);
        GLES20.glAttachShader(mProgramHandle,mFragmentShader);
        GLES20.glBindAttribLocation(mProgramHandle,0,"a_Position");
        GLES20.glBindAttribLocation(mProgramHandle,1,"a_Color");
        GLES20.glBindAttribLocation(mProgramHandle,2,"a_Normal");
        GLES20.glBindAttribLocation(mProgramHandle,3,"a_TextureCoordinate");
        GLES20.glLinkProgram(mProgramHandle);
        int[] linkStatus = new int[1];
        GLES20.glGetProgramiv(mProgramHandle,GLES20.GL_LINK_STATUS,linkStatus,0);
        if(linkStatus[0] == GLES20.GL_FALSE)
        {
            Log.e(TAG,GLES20.glGetProgramInfoLog(mProgramHandle));
            GLES20.glDeleteProgram(mProgramHandle);
            mProgramHandle = 0 ;
        }

        String mPointVertexSource = loadAssertFile("point_vertex_shader.glsl");
        String mPointFragmentSource = loadAssertFile("point_fragment_shader.glsl");
        int mPointVertexShader = loadShader(GLES20.GL_VERTEX_SHADER,mPointVertexSource);
        int mPointFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,mPointFragmentSource);
        if(mPointVertexShader == 0 )
            throw new RuntimeException("Error create the point of vertex shader ! ");
        if(mPointFragmentShader == 0 )
            throw new RuntimeException("Error create the point of fragment shader ! ");
        mPointProgramHandle = GLES20.glCreateProgram();
        if(mPointProgramHandle == 0 )
            throw new RuntimeException("Error create the point of program !");
        GLES20.glAttachShader(mPointProgramHandle,mPointVertexShader);
        GLES20.glAttachShader(mPointProgramHandle,mPointFragmentShader);
        GLES20.glBindAttribLocation(mPointProgramHandle,0,"a_Position");
        GLES20.glLinkProgram(mPointProgramHandle);
        int[] mPointLinkStatus = new int[1];
        GLES20.glGetProgramiv(mPointProgramHandle,GLES20.GL_LINK_STATUS,mPointLinkStatus,0);
        if(mPointLinkStatus[0] == GLES20.GL_FALSE)
        {
            Log.e(TAG,GLES20.glGetProgramInfoLog(mPointProgramHandle));
            GLES20.glDeleteProgram(mPointProgramHandle);
            mPointProgramHandle = 0 ;
        }

        //加载纹理图片
        mTextureDataHandle = loadTexture(R.drawable.bumpy_bricks_public_domain);

    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {

        GLES20.glViewport(0,0,width,height);
        float ratio = (float)width/height;
        Matrix.frustumM(mProjectionMatrix,0,-ratio,ratio,-1,1,1,10);
    }

    @Override
    public void onDrawFrame(GL10 gl) {

        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT| GLES20.GL_COLOR_BUFFER_BIT);

        long time = SystemClock.uptimeMillis() % 10000L;
        float angleDegree = (360.0f/ 10000.0f) * time;
        GLES20.glUseProgram(mProgramHandle);

        // set program handle form cube drawing.
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle,"u_MVPMatrix");
        mViewMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle,"u_ViewMatrix");
        mLightPositionHandle = GLES20.glGetUniformLocation(mProgramHandle,"u_LightPosition");
        mTextureHandle = GLES20.glGetUniformLocation(mProgramHandle,"u_Texture");
        mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle,"a_Position");
        mColorHandle = GLES20.glGetAttribLocation(mProgramHandle,"a_Color");
        mNormalHandle = GLES20.glGetAttribLocation(mProgramHandle,"a_Normal");
        mTextuereCoordinateHandle = GLES20.glGetAttribLocation(mProgramHandle,"a_TextureCoordinate");
        //set the active texture unit to texture  unit 0.
        GLES20.glActiveTexture(GLES20.GL_TEXTURE);
        //bind the texture to this unit.
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,mTextureDataHandle);
        //tell the texture uniform sampler to use this texture int the shader by binding to texture unit 0 .
        GLES20.glUniform1i(mTextureHandle,0);

        //calculate position of the light. rotate and then push into the distance.
        Matrix.setIdentityM(mLightModelMatrix,0);
        Matrix.translateM(mLightModelMatrix,0,0.0f,0.0f,-5.0f);
        Matrix.rotateM(mLightModelMatrix,0,angleDegree,0.0f,1.0f,0.0f);
        Matrix.translateM(mLightModelMatrix,0,0.0f,0.0f,2.0f);

        Matrix.multiplyMV(mLightPositionWorld,0,mLightModelMatrix,0,
                mLightPositionModel,0);
        Matrix.multiplyMV(mLightPositionEys,0,mViewMatrix,0,mLightPositionWorld,0);
        //draw some cubes.
        Matrix.setIdentityM(mModelMatrix,0);
        Matrix.translateM(mModelMatrix,0,4.0f,0.0f,-7.0f);
        Matrix.rotateM(mModelMatrix,0,angleDegree,1.0f,0.0f,0.0f);

        drawCube();

        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.translateM(mModelMatrix, 0, -4.0f, 0.0f, -7.0f);
        Matrix.rotateM(mModelMatrix, 0, angleDegree, 0.0f, 1.0f, 0.0f);
        drawCube();

        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.translateM(mModelMatrix, 0, 0.0f, 4.0f, -7.0f);
        Matrix.rotateM(mModelMatrix, 0, angleDegree, 0.0f, 0.0f, 1.0f);
        drawCube();

        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.translateM(mModelMatrix, 0, 0.0f, -4.0f, -7.0f);
        drawCube();

        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.translateM(mModelMatrix, 0, 0.0f, 0.0f, -5.0f);
        Matrix.rotateM(mModelMatrix, 0, angleDegree, 1.0f, 1.0f, 0.0f);
        drawCube();


        //draw a point to indicate the light.
        GLES20.glUseProgram(mPointProgramHandle);
        drawLight();


    }

    private void drawLight() {

        int mPointMVPMatrixHandle = GLES20.glGetUniformLocation(mPointProgramHandle,"u_MVPMatrix");
        int mPointPositionHandle = GLES20.glGetAttribLocation(mPointProgramHandle,"a_Position");

        GLES20.glVertexAttrib3f(mPointPositionHandle,mLightPositionEys[0],mLightPositionEys[1],mLightPositionEys[2]);
        GLES20.glEnableVertexAttribArray(mPointPositionHandle);

        Matrix.multiplyMM(mMVPMatrix,0,mViewMatrix,0,mLightModelMatrix,0);
        Matrix.multiplyMM(mMVPMatrix,0,mProjectionMatrix,0,
                mMVPMatrix,0);
        GLES20.glUniformMatrix4fv(mPointMVPMatrixHandle,1,false,mMVPMatrix,0);
        GLES20.glDrawArrays(GLES20.GL_POINTS,0,1);
    }

    private void drawCube() {

        mCubePosition.position(0);
        GLES20.glVertexAttribPointer(mPositionHandle,mPositionDataSize,GLES20.GL_FLOAT,false,0,mCubePosition);
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        mCubeColor.position(0);
        GLES20.glVertexAttribPointer(mColorHandle,mColorDataSize,GLES20.GL_FLOAT,false,
                0,mCubeColor);
        GLES20.glEnableVertexAttribArray(mColorHandle);

        mCubeNormal.position(0);
        GLES20.glVertexAttribPointer(mNormalHandle,mNormalDataSize,GLES20.GL_FLOAT,false,0,mCubeNormal);
        GLES20.glEnableVertexAttribArray(mNormalHandle);

        mCubeTexture.position(0);
        GLES20.glVertexAttribPointer(mTextuereCoordinateHandle,mTextuewCoordinateDataSize,GLES20.GL_FLOAT,false,0,mCubeTexture);
        GLES20.glEnableVertexAttribArray(mTextuereCoordinateHandle);

        Matrix.multiplyMM(mMVPMatrix,0,mViewMatrix,0,mModelMatrix,0);
        Matrix.multiplyMM(mMVPMatrix,0,mProjectionMatrix,0,mMVPMatrix,0);
        // pass in the model matrix
        GLES20.glUniformMatrix4fv(mViewMatrixHandle,1,false,mMVPMatrix,0);
        //pass in the combined matrix.
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle,1,false,mMVPMatrix,0);
        // pass in the light position in the eye space.
        GLES20.glUniform3f(mLightPositionHandle,mLightPositionEys[0],mLightPositionEys[1],mLightPositionEys[2]);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,36);


    }

    private int loadShader(int type,String source)
    {
        int shader = GLES20.glCreateShader(type);
        if(shader == 0 )
            throw new RuntimeException("Error create shader !");
        GLES20.glShaderSource(shader,source);
        GLES20.glCompileShader(shader);
        int[] status = new int[1];
        GLES20.glGetShaderiv(shader,GLES20.GL_COMPILE_STATUS,status,0);
        return shader;
    }

    private String loadAssertFile(String path)
    {
        String response = null;
        try{
            InputStream in = mContext.getAssets().open(path);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int lenght = 0;
            lenght = in.read(buffer);
            while(lenght > 0 )
            {
                out.write(buffer,0,lenght);
                lenght = in.read(buffer);
            }
            response =  out.toString();
            response = response.replaceAll("\\r\\n","\n");
            in.close();
            out.close();
        }catch(Exception e)
        {
            Log.e(TAG,"加载Assert 文件中的文件失败！ - >" + e.getMessage());
            return response;
        }
        return response;
    }

    private int loadTexture(int resourceID)
    {
        int[] mTextureHandle = new int[1];
        GLES20.glGenTextures(1,mTextureHandle,0);
        if(mTextureHandle[0] == 0 )
            throw new RuntimeException("Error generating texture name.");
        BitmapFactory.Options mOptions = new BitmapFactory.Options();
        mOptions.inScaled = false; // no pre-scaling.
        Bitmap mBitmap = BitmapFactory.decodeResource(mContext.getResources(),resourceID,mOptions);
        // bind to the texture in openGL.
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,mTextureHandle[0]);
        // set filtering.
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_NEAREST);
        //load the bitmap into the bound texture.
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,mBitmap,0);
        mBitmap.recycle();
        return mTextureHandle[0];
    }


}
